// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX                                                                           XX
XX                 State machine used in the JIT                             XX
XX                                                                           XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/

#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif

#include "smcommon.cpp"

//
// The array to map from EE opcodes (i.e. CEE_ ) to state machine opcodes (i.e. SM_ )
//
const SM_OPCODE smOpcodeMap[] = {
#define OPCODEMAP(eename, eestring, smname) smname,
#include "smopcodemap.def"
#undef OPCODEMAP
};

// ????????? How to make this method inlinable, since it refers to smOpcodeMap????
/* static */ SM_OPCODE CodeSeqSM::MapToSMOpcode(OPCODE opcode)
{
    assert(opcode < CEE_COUNT);

    SM_OPCODE smOpcode = smOpcodeMap[opcode];
    assert(smOpcode < SM_COUNT);
    return smOpcode;
}

void CodeSeqSM::Start(Compiler* comp)
{
    pComp          = comp;
    States         = gp_SMStates;
    JumpTableCells = gp_SMJumpTableCells;
    StateWeights   = gp_StateWeights;
    NativeSize     = 0;

    Reset();
}

void CodeSeqSM::Reset()
{
    curState = SM_STATE_ID_START;
}

void CodeSeqSM::End()
{
    if (States[curState].term)
    {
        TermStateMatch(curState DEBUGARG(pComp->verbose));
    }
}

void CodeSeqSM::Run(SM_OPCODE opcode DEBUGARG(int level))
{
    SM_STATE_ID nextState;
    SM_STATE_ID rollbackState;

    SM_OPCODE opcodesToRevisit[MAX_CODE_SEQUENCE_LENGTH];

    assert(level <= MAX_CODE_SEQUENCE_LENGTH);

_Next:
    nextState = GetDestState(curState, opcode);

    if (nextState != 0)
    {
        // This is easy, Just go to the next state.
        curState = nextState;
        return;
    }

    assert(curState != SM_STATE_ID_START);

    if (States[curState].term)
    {
        TermStateMatch(curState DEBUGARG(pComp->verbose));
        curState = SM_STATE_ID_START;
        goto _Next;
    }

    // This is hard. We need to rollback to the longest matched term state and restart from there.

    rollbackState = States[curState].longestTermState;
    TermStateMatch(rollbackState DEBUGARG(pComp->verbose));

    assert(States[curState].length > States[rollbackState].length);

    unsigned numOfOpcodesToRevisit = States[curState].length - States[rollbackState].length + 1;
    assert(numOfOpcodesToRevisit > 1 &&
           numOfOpcodesToRevisit <= MAX_CODE_SEQUENCE_LENGTH); // So it can fit in the local array opcodesToRevisit[]

    SM_OPCODE* p = opcodesToRevisit + (numOfOpcodesToRevisit - 1);

    *p = opcode;

    // Fill in the local array:
    for (unsigned i = 0; i < numOfOpcodesToRevisit - 1; ++i)
    {
        *(--p)   = States[curState].opc;
        curState = States[curState].prevState;
    }

    assert(curState == rollbackState);

    // Now revisit these opcodes, starting from SM_STATE_ID_START.
    curState = SM_STATE_ID_START;
    for (p = opcodesToRevisit; p < opcodesToRevisit + numOfOpcodesToRevisit; ++p)
    {
        Run(*p DEBUGARG(level + 1));
    }
}

SM_STATE_ID CodeSeqSM::GetDestState(SM_STATE_ID srcState, SM_OPCODE opcode)
{
    assert(opcode < SM_COUNT);

    JumpTableCell* pThisJumpTable = (JumpTableCell*)(((PBYTE)JumpTableCells) + States[srcState].jumpTableByteOffset);

    JumpTableCell* cell = pThisJumpTable + opcode;

    if (cell->srcState != srcState)
    {
        assert(cell->srcState == 0 ||
               cell->srcState != srcState); // Either way means there is not outgoing edge from srcState.
        return 0;
    }
    else
    {
        return cell->destState;
    }
}

#ifdef DEBUG

const char* CodeSeqSM::StateDesc(SM_STATE_ID stateID)
{
    static char      s_StateDesc[500];
    static SM_OPCODE s_StateDescOpcodes[MAX_CODE_SEQUENCE_LENGTH];

    if (stateID == 0)
    {
        return "invalid";
    }
    if (stateID == SM_STATE_ID_START)
    {
        return "start";
    }
    unsigned i = 0;

    SM_STATE_ID b = stateID;

    while (States[b].prevState != 0)
    {
        s_StateDescOpcodes[i] = States[b].opc;
        b                     = States[b].prevState;
        ++i;
    }

    assert(i == States[stateID].length && i > 0);

    *s_StateDesc = 0;

    while (--i > 0)
    {
        strcat(s_StateDesc, smOpcodeNames[s_StateDescOpcodes[i]]);
        strcat(s_StateDesc, " -> ");
    }

    strcat(s_StateDesc, smOpcodeNames[s_StateDescOpcodes[0]]);

    return s_StateDesc;
}

#endif // DEBUG
